Skip to content

Portfolio Monitor ability#271

Open
hassan1731996 wants to merge 1 commit into
openhome-dev:devfrom
hassan1731996:feature/portfolio-monitor
Open

Portfolio Monitor ability#271
hassan1731996 wants to merge 1 commit into
openhome-dev:devfrom
hassan1731996:feature/portfolio-monitor

Conversation

@hassan1731996
Copy link
Copy Markdown
Contributor

@hassan1731996 hassan1731996 commented May 16, 2026

Portfolio Monitor

A passive background ability that tracks your stock portfolio in real time, fires proactive alerts when positions move beyond your thresholds, and delivers a full P&L breakdown on demand — all by voice.

What's in this PR

Core features

  • Background daemon polls every 5 min during market hours, sleeps 30 min outside — no wasted API calls
  • Price cache with 3-min TTL prevents redundant fetches
  • Morning open summary, live price alerts (per-direction, once per day), end-of-day wrap-up
  • LLM intent router handles natural language reliably for all intents

Interactive intents

  • PORTFOLIO — snapshot (value, day P&L, overall P&L) → hub navigation
  • CHECK — specific stock with position P&L, follow-up loop
  • COMPARE — day-over-day vs yesterday's close, chunked at 4 stocks
  • MOVERS — biggest gainer and loser in your portfolio
  • MARKET — live S&P 500, Nasdaq, Dow Jones pulse
  • ADD — one-shot or prompted; offers UPDATE if stock already exists
  • UPDATE — bought more (weighted avg cost recalc), sold some (share reduction or full remove), correct/overwrite
  • SET_ALERT — drop/rise % thresholds per stock, loop for multiple
  • REMOVE — with confirmation
  • CLEAR — with confirmation

Voice UX

  • Hub navigation uses LLM sub-router — handles any phrasing consistently
  • BREAKDOWN and COMPARE chunk at 4 stocks per speak, paginate with "Want to hear the rest?"
  • All add/check/alert flows loop so you can do multiple in one conversation

Setup

  • Finnhub API key (free, 60 calls/min) — required
  • Alpha Vantage key (optional fallback, 25 calls/day free)
  • Both added in OpenHome Settings → API Keys

What changed since initial review

  • Replaced all keyword-matching intent routing with LLM router — more reliable on natural phrasing
  • Added COMPARE intent with day-over-day view
  • Rewrote portfolio hub as a feature-centric flow with 2-line snapshot + navigation
  • Added UPDATE intent (bought more / sold some / correct) — handles all position change flows
  • Added MARKET intent — S&P 500, Nasdaq, Dow via same Finnhub feed
  • Hub loop now uses LLM sub-router (_classify_hub_action) for consistency
  • ADD flow no longer dead-ends on existing stock — offers UPDATE instead
  • Chunked voice output for BREAKDOWN and COMPARE (4 stocks, paginated)
  • TOCTOU-safe storage (try update, fallback to create)
  • does_match is fully synchronous — no LLM, no I/O
  • resume_normal_flow in finally block, daemon startup only
  • API keys via get_api_keys() at runtime, not hardcoded
  • README updated with example conversation, trigger phrases for all intents

Files

  • community/portfolio-monitor/main.py — foreground skill
  • community/portfolio-monitor/background.py — background daemon
  • community/portfolio-monitor/README.md — setup, trigger phrases, example conversation

@hassan1731996 hassan1731996 requested review from a team as code owners May 16, 2026 20:08
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

🔀 Branch Merge Check

PR direction: feature/portfolio-monitordev

Passedfeature/portfolio-monitordev is a valid merge direction

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

✅ Community PR Path Check — Passed

All changed files are inside the community/ folder. Looks good!

@github-actions github-actions Bot added the community-ability Community-contributed ability label May 16, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

✅ Ability Validation Passed

📋 Validating: community/portfolio-monitor
  ✅ All checks passed!

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 16, 2026

🔍 Lint Results

__init__.py — Empty as expected

Files linted: community/portfolio-monitor/background.py community/portfolio-monitor/main.py

✅ Flake8 — Passed

✅ All checks passed!

Copy link
Copy Markdown
Contributor

@uzair401 uzair401 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Portfolio Monitor — Issues to Address

Tested end-to-end. The architecture (interactive skill + background daemon) is solid, but a handful of issues need addressing before merge. Listed roughly by impact:

  1. One-shot handler + narrow does_match lets the agent LLM hallucinate ability behavior. _run() handles a single intent then calls resume_normal_flow(), so follow-ups like "add stock", "google", "also add nvidia" don't re-pass does_match and the agent LLM invents fake confirmations ("Great choice! Google is now in your portfolio") while no state changes — daemon logs show 0 holding(s) throughout. Fix: expand HOTWORDS, drop the "stock" not in t exclusion in the add regex, and make _handle_add / _handle_set_alert conversational with explicit-exit loops. Pattern reference: official/audius-music-dj.

  2. File I/O doesn't persist across triggers. _save_data does delete_file → write_file with a bare except — any silent failure loses the file. Reproduced: added stocks, retriggered, "No stocks yet." Switch to Context Storage (get_single_key / create_key / update_key) — same API audius-music-dj uses for favorites and global_context. Docs: capability-worker.md.

  3. Hardcoded API keys (FINNHUB_KEY = "your_finnhub_api_key" etc.) with README telling users to edit main.py. Use the SDK pattern — single-key call returning the value directly:

    self.finnhub_key = self.capability_worker.get_api_keys("finnhub_api_key") or ""
    self.av_key = self.capability_worker.get_api_keys("alphavantage_api_key") or ""

    Add an upfront check in _run() that speaks a clear message when no keys are configured.
    Docs reference: - https://docs.openhome.com/building-abilities/how-to-build#custom-api-keys-third-party-services

  4. Silent correctness bugs:

    • main.py:387(position_pnl / position_cost * 100) if position_cost else 0 is a dead expression; per-stock P&L % is never spoken.
    • main.py:470pos_pnl / pos_value should be pos_pnl / pos_cost. A stock that doubled reports "50%" instead of "100%".
    • _handle_set_alert resets the per-ticker dict to {} before setting either threshold — setting drop and rise in separate sessions loses the earlier one. Use setdefault(ticker, {}) + is not None checks.
    • does_match calls _resolve_ticker which falls through to _resolve_ticker_llmtext_to_text_response fires on every utterance, burning LLM quota. does_match must stay synchronous and cheap.
  5. README claims that don't hold up:

    • Daemon caps at 10 holdings (MAX_API_CALLS_PER_POLL) — stocks past index 10 never refresh or alert.
    • _handle_portfolio only fetches when ticker is absent from cache — stale entries are never refreshed during a session.
    • EOD summary reads from the cache; misses closing-bell moves. Force a fresh fetch in the EOD window.
    • _handle_check skips saving cache for non-portfolio stocks — re-hits API every time.

Let's bring this ability into full compliance with the SDK and voice UX requirements. Once fixed, we'd like to consider it for marketplace release — please update main.py and background.py accordingly to the best of your knowledge and best abilities practices.

@hassan1731996 hassan1731996 force-pushed the feature/portfolio-monitor branch from 062edda to 13731a6 Compare May 19, 2026 09:46
@hassan1731996
Copy link
Copy Markdown
Contributor Author

hassan1731996 commented May 19, 2026

Hey @uzair401 - appreciate the thorough review, really helpful catching these before merge.

Went through everything you flagged and pushed fixes for all of it:

The LLM hallucination issue was the biggest one - _handle_add, _handle_check, and _handle_set_alert are all conversational now with explicit while True loops and exit detection, so the user stays in the skill for follow-ups instead of falling back to the agent. Also dropped the "stock" not in t exclusion and expanded the hotwords so routing is more reliable.

Switched the whole persistence layer over to Context Storage (get_single_key / create_key / update_key) in both files. The old file I/O approach was definitely the root cause of stocks not surviving across triggers.

API keys are now loaded at runtime via get_api_keys() - nothing hardcoded. Added an upfront check in _run() that tells the user exactly what to do if the Finnhub key is missing, and updated the README to point to Settings → API Keys instead of telling people to edit source files.

Fixed all four of the silent correctness bugs - the dead P&L expression, the pos_value vs pos_cost denominator, the _handle_set_alert reset issue (now uses setdefault(ticker, {}) + is not None), and does_match no longer touches the LLM at all (static map + regex only).

On the daemon side: EOD summary now forces a fresh fetch before computing totals, _handle_portfolio does TTL-aware cache refreshes (not just missing-key checks), _handle_check saves the cache after a fresh fetch, and MAX_API_CALLS_PER_POLL is raised to 50 so larger portfolios don't silently stall.

Let me know if anything else needs attention.

@hassan1731996 hassan1731996 requested a review from uzair401 May 19, 2026 09:51
Copy link
Copy Markdown
Contributor

@uzair401 uzair401 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @hassan1731996,
This is a great ability and your contribution is much appreciated — I'd like to push it a bit further so it's marketplace-ready. A few directions I'd like us to take it:

Make it a one-stop place for stocks. Beyond just looking things up, it should also track and monitor the user's portfolio, provide live market insights, show current stock status, and do a basic day-over-day comparison (no need to go deeper than that for now).

Replace regex-based routing with an LLM intent router. Regex routing tends to be fragile for an ability this complex. Using the LLM as the intent router will route user requests much more reliably.

Restructure main.py to be more feature-centric. When the ability is triggered, the user should be able to check their portfolio, check current stock status, and view the previous day's comparison — all from one entry point.
Tighten intent handling. The check / move (or change) / delete flows need to work properly and consistently through the LLM router.

Once these are in, we'll do rigorous testing before submitting for marketplace approval. Thanks again for the work on this!

@hassan1731996
Copy link
Copy Markdown
Contributor Author

hassan1731996 commented May 19, 2026

Hey @uzair401 - pushed another round of changes based on your second review.

LLM intent router is in. _classify_intent now routes through text_to_text_response with a clear prompt covering all 8 intents. The only thing still handled by regex is CLEAR and REMOVE - those are unambiguous and destructive, so there's no reason to burn an LLM call on them. Everything else ("what happened to my Google position", "how did my stocks do today") gets routed correctly now.

Day-over-day compare is a proper feature now. Saying "compare my stocks", "day over day", or "versus yesterday" triggers _handle_compare, which reads prev_close from the cached quote data and speaks each holding's move - direction, percent, and dollar change. Added the trigger phrases to HOTWORDS and the README.

Feature-centric entry point - _handle_portfolio is now a hub. First thing you hear is a tight two-line snapshot: total value, today's P&L, overall P&L. Then it prompts: say 'breakdown' for full detail, 'compare' for day-over-day, 'movers', or a stock name. The full per-stock breakdown is still there - just moved to _speak_portfolio_breakdown and called on demand. This way the first response is fast and you can drill into whatever you actually want.

Let me know how testing goes.

@hassan1731996 hassan1731996 requested a review from uzair401 May 19, 2026 11:50
Copy link
Copy Markdown
Contributor

@uzair401 uzair401 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @hassan1731996 for the quick turnaround on the requested changes. There are still a few improvements that need to be made, and I'll keep the PR open until those changes are in. I'll work through these, and once they're finalized by our reviewer and QA team, I'll share an update along with the revised code and updated README. After that, we'll proceed with marketplace approval. Thanks again for contributing to OpenHome!

@hassan1731996
Copy link
Copy Markdown
Contributor Author

Thanks @hassan1731996 for the quick turnaround on the requested changes. There are still a few improvements that need to be made, and I'll keep the PR open until those changes are in. I'll work through these, and once they're finalized by our reviewer and QA team, I'll share an update along with the revised code and updated README. After that, we'll proceed with marketplace approval. Thanks again for contributing to OpenHome!

Hey @uzair401 - glad the changes landed well. Happy to keep iterating if there's anything specific you want me to adjust on my end in the meantime. Looking forward to seeing what the QA pass surfaces - and appreciate you taking it the rest of the way to marketplace. Exciting milestone for this one.

@hassan1731996
Copy link
Copy Markdown
Contributor Author

hassan1731996 commented May 19, 2026

Few more additions in this push:

UPDATE intent - full position management in one flow: bought more shares (recalculates weighted average cost), sold some (reduces share count, or removes the stock entirely if you sell all of it with confirmation), and correct/overwrite if the numbers are just wrong. Reachable directly via voice ("I bought more Tesla", "sold some Apple") or from within the ADD flow when a stock already exists.

MARKET intent - "how are the markets" pulls live S&P 500, Nasdaq, and Dow Jones from the same Finnhub feed. Works both as a standalone trigger and as a navigation option inside the portfolio hub.

Hub navigation → LLM sub-router - unified the portfolio dashboard's follow-up loop with the same LLM routing approach used at the top level. Both now go through the same pattern so phrasing is handled consistently throughout.

Chunked voice output - BREAKDOWN and COMPARE speak 4 stocks at a time and ask "Want to hear the rest?" before continuing. Keeps things natural for larger portfolios instead of reading everything out at once.

README example conversation - added a full walkthrough: portfolio hub, market check, per-stock query, breakdown pagination, position update, and a background alert firing.

Let me know if you have any other thoughts.

@hassan1731996 hassan1731996 requested a review from uzair401 May 21, 2026 12:55
@hassan1731996 hassan1731996 force-pushed the feature/portfolio-monitor branch from d7d0918 to 4bf04ff Compare May 31, 2026 07:18
@hassan1731996 hassan1731996 force-pushed the feature/portfolio-monitor branch from 4bf04ff to bb1122e Compare May 31, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

community-ability Community-contributed ability

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants